package com.wosai.register.repository.jdbc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import com.wosai.register.model.Terminal;
import com.wosai.register.repository.TerminalRepository;
import com.wosai.register.util.ExceptionUtil;
import com.wosai.register.util.StringUtil;

@Repository
public class JdbcTerminalRepository implements TerminalRepository {
    private final static Log logger = LogFactory.getLog(JdbcTerminalRepository.class);

    private NamedParameterJdbcTemplate npjt;
    private SimpleJdbcInsert sji;

    private final static String SELECT_BY_ID = "select * from TERMINAL where merchant_id = :merchantId and terminal_id = :terminalId ";
    private final static String SELECT_ALL = "select * from TERMINAL";

    private final static String DELETE = "delete from TERMINAL where merchant_id = :merchantId and terminal_id = :terminalId ";

    private final static String UPDATE = "update TERMINAL set id=:id, name=:name, address=:address, phone_number=:phoneNumber, " +
    		                             "introduction=:introduction, admin_password=:adminPassword, " +
    		                             "card_login_password=:cardLoginPassword, print_copies=:printCopies, " +
    		                             "reserve1=:reserve1, reserve2=:reserve2, " +
    		                             "reserve3=:reserve3, reserve4=:reserve4, " +
    		                             "cash_switch=:cashSwitch, activation_code=:activationCode, " +
    		                             "branch_name=:branchName " +
                                         "where merchant_id=:merchantId and terminal_id=:terminalId";
    
    private final static String SELECT_BY_ACTIVATION_CODE = "select * from TERMINAL where activation_code = :activationCode";
    private final static String CLEAR_ACTIVATION = "update TERMINAL set activation_code = null where activation_code = :activationCode";
    @Autowired
    public JdbcTerminalRepository(DataSource dataSource) {
        this.sji = new SimpleJdbcInsert(dataSource)
                            .withTableName("TERMINAL");

        this.npjt = new NamedParameterJdbcTemplate(dataSource);
    }

    @Override
    public Terminal findById(String merchantId, String branchId, String terminalId)
    {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("merchantId", merchantId);
        params.put("branchId", branchId);
        params.put("terminalId", terminalId);
        
        return this.npjt.queryForObject(SELECT_BY_ID, params, ParameterizedBeanPropertyRowMapper.newInstance(Terminal.class));
    }

    @Override
    public List<String> insertBatch(List<Terminal> terminals) {
        SqlParameterSource[] batch = new SqlParameterSource[terminals.size()];
        for(int i=0; i<batch.length; ++i)
            batch[i] = new BeanPropertySqlParameterSource(terminals.get(i));

        ArrayList<String> failed = new ArrayList<String>();
        try{
            int[] rows = this.sji.executeBatch(batch);
            logger.info("batch inserted " + StringUtil.intArrayToString(rows));
        }catch(DataAccessException dae){
            List<Integer> failedIndices = ExceptionUtil.extractFailedStatements(dae);
            for(int i: failedIndices){
                failed.add(terminals.get(i).getId());
            }
            logger.warn("error in batch insert " + failedIndices);
            logger.warn("detailed error info " + ExceptionUtils.getStackTrace(dae));
        }

        return failed;

    }

    @Override
    public List<String> updateBatch(List<Terminal> terminals) {
        SqlParameterSource[] batch = new SqlParameterSource[terminals.size()];
        for(int i=0; i<batch.length; ++i)
            batch[i] = new BeanPropertySqlParameterSource(terminals.get(i));

        ArrayList<String> failed = new ArrayList<String>();
        try{
            int[] rows = this.npjt.batchUpdate(UPDATE, batch);
            logger.info("batch updated " + StringUtil.intArrayToString(rows));
        }catch(DataAccessException dae){
            List<Integer> failedIndices = ExceptionUtil.extractFailedStatements(dae);
            for(int i: failedIndices){
                failed.add(terminals.get(i).getId());
            }
            logger.warn("error in batch update " + failedIndices);
            logger.warn("detailed error info " + ExceptionUtils.getStackTrace(dae));
        }

        return failed;
        
    }

    @Override
    public void update(Terminal terminal) {
        SqlParameterSource paramSource = new BeanPropertySqlParameterSource(terminal);

        int rows = this.npjt.update(UPDATE, paramSource);
        
        logger.info("updated " + rows);        
        
    }

    @Override
    public void delete(String merchantId, String branchId, String terminalId) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("merchantId", merchantId);
        params.put("branchId", branchId);
        params.put("terminalId", terminalId);
        
        int rows = this.npjt.update(DELETE, params);
        logger.info("deleted " + rows);
        
    }

    @Override
    public Terminal findByActivationCode(String code) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("activationCode", code);
        
        List<Terminal> terminals = this.npjt.query(SELECT_BY_ACTIVATION_CODE, params, ParameterizedBeanPropertyRowMapper.newInstance(Terminal.class));
        if (terminals.size()==0) {
            throw new RuntimeException("Unable to find a terminal given activation code " + code);
        }
        if (terminals.size()>1){
            throw new RuntimeException("Found multiple terminals given activation code " + code);
        }
        return terminals.get(0);
        
    }

    @Override
    public void clearActivationCode(String code) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("activationCode", code);
        
        this.npjt.update(CLEAR_ACTIVATION, params);
    }


}
